/** js扩展函数
 * @file jsfuncs.hpp
 * @author Kiven Lee
 * @version 1.0
 * @date 2021-11-18
 *
 * @copyright Kivensoft (c) 2018 - 2021
 *
 */

#include "jsfuncs.hpp"
#include "config.hpp"
#include "klib/log.hpp"
#include "klib/jsonwriter.hpp"
#include "klib/file.hpp"
#include "kwin/strconv.hpp"

using namespace miniplayer;

namespace {
    jsValue err_impl(jsExecState es, size_t N, const char* fmt, ...) {
        klib::dstring_t<256> buf;
        const char* msg;

        if (N) {
            va_list va;
            va_start(va, fmt);
            buf.vprintf(fmt, va);
            va_end(va);
            msg = buf.data();
        } else {
            msg = fmt;
        }

        jsValue ret = jsEmptyObject(es);
        jsSet(es, ret, "code", jsInt(1));
        jsSet(es, ret, "message", jsString(es, msg));
        return ret;
    }

    template<typename... ARGS>
    inline jsValue err(jsExecState es, const char* fmt, ARGS&&... args) {
        return err_impl(es, sizeof...(args), fmt, std::forward(args)...);
    }

    jsValue ok(jsExecState es, jsValue data) {
        jsValue ret = jsEmptyObject(es);
        jsSet(es, ret, "code", jsInt(0));
        jsSet(es, ret, "data", data);
        return ret;
    }
}

struct js_data_t : public jsData {
    js_data_t() {
        this->typeName[0] = 0;
        this->propertyGet = nullptr;
        this->propertySet = nullptr;
        this->finalize = nullptr;
        this->callAsFunction = nullptr;
    }

    js_data_t(jsGetPropertyCallback get_func, jsSetPropertyCallback set_func = nullptr, jsFinalizeCallback del_func = nullptr) {
        typeName[0] = 0;
        propertyGet = get_func;
        propertySet = set_func;
        finalize = del_func;
        callAsFunction = nullptr;
    }

    js_data_t(jsCallAsFunctionCallback func) {
        typeName[0] = 0;
        propertyGet = nullptr;
        propertySet = nullptr;
        finalize = nullptr;
        callAsFunction = func;
    }
};

// 互相调用：JS调用CPP
WKE_CALL_TYPE jsValue miniplayer::js_msgbox(jsExecState es, void* param) {
    const char* text = jsToTempString(es, jsArg(es, 0));
    Config::messagebox(text);
    return jsUndefined();
}

// 关闭应用程序
WKE_CALL_TYPE jsValue miniplayer::js_quitApp(jsExecState es, void* param) {
    ::PostMessageA(Config::main_hwnd, WM_CLOSE, 0, 0);
    return jsUndefined();
}

// 提供类似javascrpt中的alert功能，仅作为演示
WKE_CALL_TYPE jsValue miniplayer::js_showWindow(jsExecState es, void* param) {
    int sw = jsToBoolean(es, jsArg(es, 0)) ? SW_SHOW : SW_HIDE;
    ::ShowWindow(wkeGetWindowHandle(Config::wke_web_view), sw);
    return jsUndefined();
}

// 列出指定目录的所有文件
WKE_CALL_TYPE jsValue miniplayer::js_listFiles(jsExecState es, void* param) {
    // 获取路径参数
    const wchar_t* path = jsToTempStringW(es, jsArg(es, 0));
    WIN32_FIND_DATAW findData;


    HANDLE findHandle = ::FindFirstFileW(path, &findData);
    // 查找失败，可能不是正确的目录名
    if (findHandle == INVALID_HANDLE_VALUE) {
        kwin::ansi_chars_t ansiPath(path);
        LOG_ERROR("listFiles失败，错误的路径：%s", ansiPath.c_str());
        return err(es, "错误的路径");
    }

    jsValue data = jsEmptyArray(es);
    int pos = 0;

    do {
        // . 和 .. 是当前目录和父目录，跳过
        wchar_t *fn = findData.cFileName;
        if (fn[0] == L'.' && (fn[1] == L'\0' || (fn[1] == L'.' && fn[2] == L'\0')))
            continue;

        bool isDir = findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
        kwin::utf8_chars_t fileName(fn);
        uint64_t fileSize = (uint64_t) ((findData.nFileSizeHigh * (MAXDWORD+1)) + findData.nFileSizeLow);

        jsValue item = jsEmptyObject(es);
        jsSet(es, item, "name", jsString(es, fileName.c_str()));
        jsSet(es, item, "isDir", jsBoolean(isDir));
        jsSet(es, item, "size", jsDouble((double)fileSize));
        jsSetAt(es, data, pos++, item);

    } while (::FindNextFileW(findHandle, &findData));

    ::FindClose(findHandle);


    return ok(es, data);
}

WKE_CALL_TYPE jsValue miniplayer::js_loadFile(jsExecState es, void* param) {
    kwin::ansi_chars_t ansi_name(jsToTempStringW(es, jsArg(es, 0)));
    klib::file_t file(ansi_name.data(), "rb");

    if (!file) return jsUndefined();
    size_t file_len = file.length();
    char* buf = new char[file_len];
    size_t read_size = file.read(buf, file_len);

    return jsArrayBuffer(es, buf, read_size);
}

// 从v8函数调用中获取第一个参数，并复制到新分配的内存中，新分配的内存将在窗口消息处理器中删除
char* vlcGetPath(jsExecState es) {
    const char *path = jsToTempString(es, jsArg(es, 0));
    char *copy = new char[std::strlen(path) + 1];
    std::strcpy(copy, path);
    return copy;
}

// 播放视频文件
WKE_CALL_TYPE jsValue miniplayer::js_vlcPlay(jsExecState es, void* param) {
    // 通过发送消息的方式，让vlc的播放和停止操作在同一个线程内，避免死锁
    ::SendMessageA(Config::main_hwnd, WM_VLC_PLAYER, (WPARAM) PlayState::play, (LPARAM) vlcGetPath(es));
    return jsUndefined();
}

// 播放视频流地址
WKE_CALL_TYPE jsValue miniplayer::js_vlcPlayUrl(jsExecState es, void* param) {
    // 通过发送消息的方式，让vlc的播放和停止操作在同一个线程内，避免死锁
    ::SendMessageA(Config::main_hwnd, WM_VLC_PLAYER, (WPARAM) PlayState::play_url, (LPARAM) vlcGetPath(es));
    return jsUndefined();
}

// 停止视频播放，并关闭vlc窗口
WKE_CALL_TYPE jsValue miniplayer::js_vlcStop(jsExecState es, void* param) {
    ::PostMessageA(Config::main_hwnd, WM_VLC_PLAYER, (WPARAM) PlayState::stop, 0);
    return jsUndefined();
}

// 暂停视频播放
WKE_CALL_TYPE jsValue miniplayer::js_vlcPause(jsExecState es, void* param) {
    ::PostMessageA(Config::main_hwnd, WM_VLC_PLAYER, (WPARAM) PlayState::pause, 0);
    return jsUndefined();
}

// 返回vlcPlayer播放对象，演示如何注册一个对象到v8中
WKE_CALL_TYPE jsValue miniplayer::js_get_vlcPlayer(jsExecState es, void* param) {
    #define PLAYER_FUNC(NAME, FUNC) \
        static js_data_t vlcPlayer_##NAME([] (jsExecState es, jsValue object, jsValue* args, int argCount) { \
                return js_vlc##FUNC(es, nullptr); })
    PLAYER_FUNC(play, Play);
    PLAYER_FUNC(playUrl, PlayUrl);
    PLAYER_FUNC(stop, Stop);
    PLAYER_FUNC(pause, Pause);
    #undef PLAYER_FUNC

    // static js_data_t vlcPalyer([] (jsExecState es, jsValue object, const char* propertyName) {
    //         #define MY_FUNC_MAP(NAME) if (!std::strcmp(#NAME, propertyName)) return jsFunction(es, &vlcPlayer_##NAME)
    //         MY_FUNC_MAP(pause);
    //         MY_FUNC_MAP(play);
    //         MY_FUNC_MAP(playUrl);
    //         MY_FUNC_MAP(stop);
    //         #undef MY_FUNC_MAP

    //         return jsUndefined();
    //     }, nullptr, nullptr);

    // return jsObject(es, &vlcPalyer);

    jsValue ret = jsEmptyObject(es);
    jsSet(es, ret, "play", jsFunction(es, &vlcPlayer_play));
    jsSet(es, ret, "playUrl", jsFunction(es, &vlcPlayer_playUrl));
    jsSet(es, ret, "stop", jsFunction(es, &vlcPlayer_stop));
    jsSet(es, ret, "pause", jsFunction(es, &vlcPlayer_pause));
    return ret;
}
